/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecmascript/tests/test_helper.h"
#include "ecmascript/jspandafile/js_pandafile_manager.h"
#include "ecmascript/napi/include/jsnapi.h"
#include "ecmascript/dfx/hprof/heap_snapshot.h"
#include "ecmascript/dfx/hprof/heap_profiler.h"

namespace panda::test {
using namespace panda::ecmascript;

class HeapDumpTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(ecmaVm, thread, scope);
        ecmaVm->GetJSThread()->GetCurrentEcmaContext()->SetRuntimeStatEnable(true);
        ecmaVm->SetEnableForceGC(false);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(ecmaVm, scope);
    }

    EcmaVM *ecmaVm {nullptr};
    EcmaHandleScope *scope {nullptr};
    JSThread *thread {nullptr};
};

struct GraphNode {
    std::vector<uint32_t> toNodeIndexs;
};

std::vector<GraphNode> BuildGraph(const CList<Node *> *nodes, const CList<Edge *> *edges)
{
    std::vector<GraphNode> graphNodes(nodes->size());
    for (auto node : *nodes) {
        graphNodes[node->GetIndex()].toNodeIndexs.reserve(node->GetEdgeCount());
    }
    for (auto edge : *edges) {
        graphNodes[edge->GetFrom()->GetIndex()].toNodeIndexs.push_back(edge->GetTo()->GetIndex());
    }
    return graphNodes;
}

void VisitNode(const std::vector<GraphNode> &graphNodes, uint32_t index, std::vector<bool> &reachables)
{
    ASSERT_TRUE(index < reachables.size());
    if (reachables[index]) {
        return;
    }
    reachables[index] = true;
    for (auto toIndex : graphNodes[index].toNodeIndexs) {
        VisitNode(graphNodes, toIndex, reachables);
    }
}

void VisitAllRoots(const std::vector<GraphNode> &graphNodes, const CList<Node *> *nodes, std::vector<bool> &reachables)
{
    auto iter = nodes->begin();
    for (uint32_t index = 0; index < graphNodes.size(); index++) {
        if (iter != nodes->end() && (*iter)->GetType() == NodeType::SYNTHETIC) {
            VisitNode(graphNodes, index, reachables);
        }
        iter++;
    }
}

const char *GetNodeTypeStr(NodeType type)
{
    static const char *typeStr[] = {
        "HIDDEN",
        "ARRAY",
        "STRING",
        "OBJECT",
        "CODE",
        "CLOSURE",
        "REGEXP",
        "HEAPNUMBER",
        "NATIVE",
        "SYNTHETIC",
        "CONSSTRING",
        "SLICEDSTRING",
        "SYMBOL",
        "BIGINT",
        "DEFAULT"
    };
    return typeStr[static_cast<int>(type)];
}

HWTEST_F_L0(HeapDumpTest, NoUnreachable)
{
    const std::string abcFileName = HPROF_TEST_ABC_FILES_DIR"heapdump.abc";
    const std::string jsFileName = HPROF_TEST_JS_FILES_DIR"heapdump.js";

    bool result = JSNApi::Execute(ecmaVm, abcFileName, "heapdump");
    EXPECT_TRUE(result);
    const_cast<Heap *>(ecmaVm->GetHeap())->CollectGarbage(TriggerGCType::FULL_GC);
    HeapProfiler profiler{ecmaVm};
    HeapSnapshot *snapshot = profiler.MakeHeapSnapshot(HeapProfiler::SampleType::ONE_SHOT, false, false, false, true);

    auto nodes = snapshot->GetNodes();
    auto strTable = snapshot->GetEcmaStringTable();
    auto edges = snapshot->GetEdges();
    ASSERT_NE(nodes, nullptr);
    ASSERT_NE(strTable, nullptr);
    ASSERT_NE(edges, nullptr);
    ASSERT_FALSE(nodes->empty());
    ASSERT_EQ(nodes->front()->GetType(), NodeType::SYNTHETIC);

    auto graphNodes = BuildGraph(nodes, edges);
    std::vector<bool> reachables(nodes->size(), false);

    VisitAllRoots(graphNodes, nodes, reachables);

    auto iter = nodes->begin();
    uint32_t unreachableCnt = 0;
    std::vector<uint32_t> typeCnts(static_cast<uint32_t>(JSType::TYPE_LAST) + 1, 0);
    LOG_ECMA(ERROR) << "JSType num: " << static_cast<uint32_t>(JSType::TYPE_LAST) + 1;
    for (uint32_t index = 0; index < reachables.size(); index++) {
        auto node = *iter;
        iter++;
        std::string objType = "(None)";
        if (node->GetAddress() == 0) {
            continue;
        }
        auto value = JSTaggedValue(node->GetAddress());
        if (!value.IsHeapObject()) {
            continue;
        }
        auto taggedObj = value.GetTaggedObject();
        auto hclass = taggedObj->GetClass();
        if (hclass == nullptr) {
            continue;
        }
        objType = ConvertToStdString(JSHClass::DumpJSType(hclass->GetObjectType()));
        if (!reachables[index]) {
            if (static_cast<uint32_t>(hclass->GetObjectType()) > static_cast<uint32_t>(JSType::TYPE_LAST)) {
                LOG_ECMA(ERROR) << "JSType index: " << static_cast<uint32_t>(hclass->GetObjectType()) 
                    << " name: " << objType;
            }
            typeCnts[static_cast<uint32_t>(hclass->GetObjectType())]++;
            unreachableCnt++;
            if (unreachableCnt <= 10) {
                LOG_ECMA(ERROR) << "Node: {id: " << node->GetId() 
                    << ", index: " << node->GetIndex() << ", type: " << GetNodeTypeStr(node->GetType())
                    << ", name: " << *node->GetName() << ", objType: " << objType << "} unreachabled";
            }
        }
    }
    LOG_ECMA(ERROR) << "[LYP] " << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl;
    EXPECT_EQ(unreachableCnt, 0);
    if (unreachableCnt != 0) {
        iter = nodes->begin();
        for (uint32_t index = 0; index < static_cast<uint32_t>(JSType::TYPE_LAST) + 1; index++) {
            auto node = *iter;
            iter++;
            if (typeCnts[index] == 0) {
                continue;
            }
            std::string objType = "(None)";
            if (node->GetAddress() == 0) {
                continue;
            }
            auto value = JSTaggedValue(node->GetAddress());
            if (!value.IsHeapObject()) {
                continue;
            }
            auto taggedObj = value.GetTaggedObject();
            auto hclass = taggedObj->GetClass();
            if (hclass == nullptr) {
                continue;
            }
            objType = ConvertToStdString(JSHClass::DumpJSType(hclass->GetObjectType()));
            LOG_ECMA(ERROR) << "JSType: " << objType << " unreachable cnt: " << typeCnts[index];
        }
    }
}
}